package starfield.modpack;

import starfield.util.JSONUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import javax.swing.JOptionPane;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.util.Zip4jConstants;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONObject;
import starfield.Application;
import starfield.Metadata;
import starfield.mod.Mod;
import starfield.ParsingException;
import starfield.ValidationException;

public class Modpack {
	public static final String MODPACKS_DIRECTORY = "modpacks";
	public static final String FILENAME_CONFIG = "modpack.config";
	public static final String EXTENSION = "sbmp";
	public static final String KEY_NAME = "name";
	
	protected String name;
	protected Metadata metadata;
	protected ArrayList<Mod> mods;
	
	protected Modpack(String name) {
		this.name = name;
		metadata = new Metadata();
		mods = new ArrayList<>();
	}
	
	public static Modpack create(ModpackDialog dialog) throws IOException, ValidationException {
		Modpack modpack = new Modpack(dialog.getDirectoryName());
		
		File directory = new File(modpack.getDirectoryPath());
		
		if (directory.exists() && directory.isDirectory()) {
			throw new IOException("Modpack directory '" + directory.getName() + "' already exists.");
		} else if (!directory.mkdirs()) {
			throw new IOException("Failed to find modpack directory, and failed to create one.");
		}
		
		dialog.fillModpack(modpack);

		modpack.save();

		Application.getModpacks().add(modpack);
		
		return modpack;
	}
	
	public static Modpack load(String directoryName) throws IOException, ParsingException {
		Modpack modpack = new Modpack(directoryName);
		modpack.load();
		return modpack;
	}
	
	protected void load() throws IOException, ParsingException  {
		deserialize(JSONUtils.parseObject(FileUtils.readFileToString(getConfigFile())));
		loadMods();		
		Application.getModpacks().add(this);
	}
	
	protected void loadMods() {
		mods.clear();
		
		for (File modFile : getModFiles())
		{
			try {
				Mod.load(this, modFile.getName());
			} catch (IOException | ParsingException ex) {
				JOptionPane.showMessageDialog(
					null, 
					"Could not load mod '" + modFile.getName() + "': " +
					System.lineSeparator() + System.lineSeparator() + 
					ex.getMessage(), 
					"Starfield", 
					JOptionPane.WARNING_MESSAGE);
			}
		}
	}
	
	public void save() throws IOException {
		FileUtils.writeStringToFile(new File(getConfigFilePath()), serialize().toJSONString());
	}
	
	public void delete() throws IOException {
		FileUtils.deleteDirectory(new File(getDirectoryPath()));
	}
	
	public void export(String path) throws ZipException, IOException {
		ZipFile zipFile = new ZipFile(path);
		ZipParameters parameters = new ZipParameters();
		parameters.setCompressionMethod(Zip4jConstants.COMP_DEFLATE);
		parameters.setCompressionLevel(Zip4jConstants.DEFLATE_LEVEL_NORMAL); 
		
		ArrayList modpackFiles = new ArrayList();
		
		modpackFiles.add(getConfigFile());
		
		for (File modFile : getModFiles())
		{
			modpackFiles.add(modFile);
		}
		
		zipFile.addFiles(modpackFiles, parameters);
	}
	
	protected void deserialize(JSONObject json) throws ParsingException {
		if (json.containsKey(KEY_NAME)) {
			String configName = JSONUtils.getString(json, KEY_NAME);
			
			if (!configName.equals(name)) {
				throw new ParsingException("Mismatch between specified name and folder name.");
			}
		} else {
			throw new ParsingException("Missing name.");
		}
		
		if (json.containsKey(Metadata.KEY_METADATA)) {
			metadata.deserialize(JSONUtils.getObject(json, Metadata.KEY_METADATA));
		}
	}
	
	protected JSONObject serialize()  {
		JSONObject json = new JSONObject();
		
		json.put(KEY_NAME, name);
		json.put(Metadata.KEY_METADATA, metadata.serialize());
		
		return json;
	}
	
	public File[] getModFiles() {
		ArrayList<File> modFilesList = new ArrayList<>();
		String[] modFileNames = new File(getDirectoryPath()).list();

		for (String modFileName : modFileNames) {
			File modFile = new File(getDirectoryPath() + File.separator + modFileName);
			
			if (modFile.isFile() && modFileName.endsWith("." + Mod.EXTENSION))
			{
				modFilesList.add(modFile);
			}
		}
		
		File[] modFilesArray = new File[modFilesList.size()];
		
		for (int i = 0; i < modFilesArray.length; i++) {
			modFilesArray[i] = modFilesList.get(i);
		}
		
		return modFilesArray;
	}
	
	public File getDirectory() throws FileNotFoundException {
		File directory = new File(getDirectoryPath());
		
		if (!directory.exists() || !directory.isDirectory()) {
			throw new FileNotFoundException("Modpack directory '" + directory.getName() + "' does not exists.");
		}
		
		return directory;
	}
	
	public File getConfigFile() throws FileNotFoundException {
		File configFile = new File(getConfigFilePath());
			
		if (!configFile.exists() || !configFile.isFile()) {
			throw new FileNotFoundException("Modpack '" + getName() + "' lacks a configuration file.");
		}
		
		return configFile;
	}
	
	public static String getDirectoryPath(String directoryName) {
		return getModpacksDirectoryPath() + File.separator + directoryName;
	}
	
	public static String getModpacksDirectoryPath() {
		return System.getProperty("user.dir") + File.separator + MODPACKS_DIRECTORY;
	}

	public String getDirectoryPath() {
		return getDirectoryPath(name);
	}
	
	public String getConfigFilePath() {
		return getDirectoryPath() + File.separator + FILENAME_CONFIG;
	}
	
	public String getName() {
		return name;
	}
	
	public static boolean ensureDirectoryName(String directoryName) {
		return directoryName != null && directoryName.matches("\\w+");
	}
	
	public static String inferDirectoryName(String title) {
		return title == null ? null : 
				StringUtils.stripAccents(title)
				.replaceAll("[\\s\\-]", "_")
				.replaceAll("[\\W]", "")
				.toLowerCase();
	}
	
	public void renameDirectory(String newDirectoryName) throws IOException {
		if (new File(getDirectoryPath()).renameTo(new File(getDirectoryPath(newDirectoryName)))) {
			name = newDirectoryName;
		} else {
			throw new IOException(
				"Could not rename modpack directory." +
				System.lineSeparator() +  
				"Please make sure no other program is editing files from it.");
		}
	}
	public Mod findMod(String directoryName) {
		for (Mod mod : getMods()) {
			if (mod.getName().equals(directoryName)) {
				return mod;
			}
		}
		
		return null;
	}
	
	public static boolean isModpackFilename(String filename) {
		return filename != null && (filename.equals(FILENAME_CONFIG) || filename.endsWith("." + Mod.EXTENSION));
	}
	
	public String toHtmlString() {
		return metadata.toHtmlString();
	}
	
	public String getDisplayName() {
		if (metadata.getTitle() != null) {
			return metadata.getTitle();
		} else {
			return name;
		}
	}

	public Metadata getMetadata() {
		return metadata;
	}
	
	public ArrayList<Mod> getMods() {
		return mods;
	}
}
